#! /usr/bin/env python

# Bugs:
# Line numbers are messed up due to file importing
# Numbers need to be syntactic sugar
# Make there be no EOF in file-read list
# Figure out how to make eval/apply work generally

import sys, os, StringIO

def extractAST(tokens):
    '''takes a single sexp, returns a python list representation (of lists and Tokens)'''
    
    if tokens[0].tokenID != Token.OPEN_PAREN_ID:
        compilerError("Expected open paren at line %d" % (tokens[0].lineno))

    elif tokens[-1].tokenID != Token.CLOSE_PAREN_ID:
        compilerError("Expected close paren at line %d" % (tokens[-1].lineno))

    ret = []
    pos = 1
    while pos < len(tokens) - 1:
        if tokens[pos].tokenID == Token.OPEN_PAREN_ID:
            close_index = findCloseParen(tokens, pos)
            if close_index == -1:
                compilerError("Expected close paren for the open paren at line %d" % tokens[pos].lineno)
            ret.append(extractAST(tokens[pos:close_index+1]))
            pos = close_index + 1
        elif tokens[pos].tokenID == Token.ID_ID:
            ret.append(tokens[pos])
            pos += 1
        else:
            compilerError("Did not expect close paren at line %d" % tokens[pos].lineno)

    return ret
    

def findCloseParen(token_list, start):
    depth = 1
    for i in range(start + 1, len(token_list)):
        if token_list[i].tokenID == Token.OPEN_PAREN_ID:
            depth += 1
        elif token_list[i].tokenID == Token.CLOSE_PAREN_ID:
            depth -= 1
        if depth == 0:
            return i
    return -1

def compilerError(message):
    print message
    sys.exit()


class Token:
    OPEN_PAREN_ID = 0
    CLOSE_PAREN_ID = 1
    ID_ID = 2
    def __init__(self, tokenID, lexeme, lineno):
        self.tokenID = tokenID
        self.lexeme = lexeme
        self.lineno = lineno
    def __str__(self):
        return self.lexeme
    def __repr__(self):
        return self.lexeme
           

def findStringCloseParen(ind, s):
    count = 0
    while True:
        ind += 1
        if s[ind] == "(":
            count += 1
        elif s[ind] == ")":
            count -= 1
        if count < 0:
            return ind

def removeSugar(s):
    '''Removes syntactic sugar from string.
    Right now this is just single quotes'''

    while s.find("'") >= 0:
        ind = s.find("'")
        if s[ind+1] == "(":
            closeParenIndex = findStringCloseParen(ind+1, s)
            s = s[:ind] + "(quote " + s[ind+1:closeParenIndex+1] + ")" + s[closeParenIndex+1:]
        else:
            endIndex = ind+1
            while (endIndex < len(s)) and (s[endIndex] not in ("(", ")")) and \
                  (s[endIndex].strip() != ""):
                if s[endIndex] == "\\":
                    endIndex += 1
                endIndex += 1
            s = s[:ind] + "(quote " + s[ind+1:endIndex] + ")" + s[endIndex:]
    return s
        

def extractTokens(lines):
    tokens = []
    lineno = 1
    for line in lines:
        
        line = line.strip()
        commentLoc = line.find(";")
        if commentLoc >= 0:
            line = line[:commentLoc]
        line = removeSugar(line.strip())
        
        i = 0
        while i < len(line):
            c = line[i]

            if c == ")": # Close Paren
                tokens.append(Token(Token.CLOSE_PAREN_ID, c, lineno))
                i += 1

            elif c == "(": # Open Paren
                tokens.append(Token(Token.OPEN_PAREN_ID, c, lineno))
                i += 1

            elif c.strip() == "": # Whitespace
                i += 1

            elif c == "\\" and line[i+1] == " ":
                tokens.append(Token(Token.ID_ID, " ", lineno))
                i += 2

            else: # Identifier
                ident = c
                #if c == "\\": # done to unescape strings
                #    ident += "\\"
                i += 1
                while (i < len(line)) and \
                      (line[i] not in ["(", ")"]) and \
                      (line[i].strip() != ""):
                    #if line[i] == "\\": # done to unescape strings
                    #    ident += "\\"
                    ident += line[i]
                    i += 1
                tokens.append(Token(Token.ID_ID, ident, lineno))
                
        lineno += 1
        
    return tokens

class SymbolTable:
    def __init__(self):
        self.symbolStack = []
    def enterScope(self):
        self.symbolStack.insert(0, {})
    def exitScope(self):
        self.symbolStack.pop(0)
    def addVar(self, name, val):
        self.symbolStack[0][name] = val
    def lookup(self, name):
        for scope in self.symbolStack:
            if name in scope:
                return scope[name]
    def depth(self, name):
        for i in range(len(self.symbolStack)):
            scope = self.symbolStack[i]
            if name in scope:
                return i
        return -1

class CgenSupport:
    STACK = "%esp"
    COUNT = "%ecx"
    BASE = "%ebp"
    ACC = "%eax"
    DATA = "%edx"
    WORD_SIZE = 4

    def paramCheck(self, e, numExpected):
        if len(e) - 1 != numExpected:
            compilerError("Expected %d parameter(s) for %s at line %d, received %d" %
                          (numExpected, e[0].lexeme, e[0].lineno, len(e) - 1))

    #Generates Code for every argument, then pushes it onto the stack
    def pushArgs(self, argList):
        for arg in argList:
            self.cgen(arg)
            self.emitPUSH(self.ACC)

    def loadFuncArgs(self, argList):
        numArgs = len(argList)

        self.emitPUSH("$%d" % (self.WORD_SIZE * (numArgs + 1))) #Room for all arguments and the previous arg pointer
        self.emitCALL("malloc")
        self.emitPOP()
        self.emitPUSH(self.ACC)

        self.emitMOV(self.WORD_SIZE, self.STACK, self.ACC) #Move previous env pointer into ACC
        self.emitMOV(0, self.STACK, self.DATA) #Move pointer to arg list into data
        self.emitMOV(self.ACC, 0, self.DATA) #Put previous env pointer onto the arg list
        
        for i in range(len(argList)):
            arg = argList[i]
            self.cgen(arg)
            self.emitMOV(0, self.STACK, self.DATA)
            self.emitMOV(self.ACC, (i + 1) * self.WORD_SIZE, self.DATA)

        self.emitPOP(self.ACC)

    def loadVar(self, e):
        loc = self.symTable.lookup(e.lexeme)

        if loc == None:
            compilerError("Cannot find variable %s at line %d" % (e.lexeme, e.lineno))

        if type(loc) == int: # Variable is an element in the argument list
            depth = self.symTable.depth(e.lexeme)
            self.emitMOV(2 * self.WORD_SIZE, self.BASE, self.ACC)#Move pointer to arg list into ACC
            for i in range(depth):
                self.emitMOV(0, self.ACC, self.ACC)
            self.emitMOV(loc, self.ACC, self.ACC)

        elif type(loc) == tuple: # Variable is a top level function (the only symbols accessible without out being passed on the arg list)
            self.emitMOV("$" + loc[0], self.ACC)
            self.allocClosure() # This doesn't seem right - to allocate to a new closure everytime we pass a top level function
            
    def cgen(self, e):
        if type(e) != list:
            self.loadVar(e)
            
        elif isinstance(e[0], Token):
            op = e[0].lexeme

            if op == "quote":
                self.paramCheck(e, 1)
                self.quote(e[1])

            elif op == "car":
                self.paramCheck(e, 1)
                self.pushArgs(e[1:])
                self.car()

            elif op == "cdr":
                self.paramCheck(e, 1)
                self.pushArgs(e[1:])
                self.cdr()

            elif op == "cons":
                self.paramCheck(e, 2)
                self.pushArgs(e[1:])
                self.cons()

            elif op == "atom":
                self.paramCheck(e, 1)
                self.pushArgs(e[1:])
                self.atom()

            elif op == "eq":
                self.paramCheck(e, 2)
                self.pushArgs(e[1:])
                self.eq()

            elif op == "cond":
                if len(e) == 1:
                    compilerError("Expected more than 0 parameters for cond at line %d" % (e[0].lineno))
                for arg in e[1:]:
                    if len(arg) != 2:
                        compilerError("Argument to cond at line %d should have exactly two elements" % (e[0].lineno))
                self.cond(e[1:])

            elif op == "lambda":
                self.paramCheck(e, 2)
                self.function(e[1], e[2], "func%d" % self.getNextFuncLabel())
                self.allocClosure()

            elif op == "define":
                self.paramCheck(e, 2)
                funcVals = cgen.symTable.lookup(e[1].lexeme)
                
                if funcVals == None:
                    argLen = len(e[2][1])
                    funcVals = (("func%d" % cgen.getNextFuncLabel()), argLen)
                    cgen.symTable.addVar(e[1].lexeme, funcVals)

                funcLabel = funcVals[0]

                self.paramCheck(e[2], 2)
                self.function(e[2][1], e[2][2], funcLabel)
                self.allocClosure()

            elif op in self.systemCalls:
                if op == "print!":
                    self.paramCheck(e, 1)
                    self.cgen(e[1])
                    self.emitCALL("pengPrint")
                    self.emitPrintAtom("\\n")
                elif op == "read-file!":
                    self.paramCheck(e, 1)
                    self.cgen(e[1])
                    self.emitCALL("readFile");

            else:
                funcVals  = self.symTable.lookup(e[0].lexeme)
                argList = e[1:]
                                
                if funcVals == None:
                    compilerError("Cannot find function %s at line %d" % (e[0].lexeme, e[0].lineno))
                
                if type(funcVals) == int: # function is a variable
                    self.cgen(e[0])
                    self.callClosure(argList)                    

                else:
                    funcLabel = funcVals[0]
                    funcLen   = funcVals[1]

                    self.paramCheck(e, funcLen)
                    
                    self.emitPUSH("$0") # environment pointer is null since all defines are in the outer most environment <- This isn't true.  Fix this if it needs to be.
                    self.loadFuncArgs(argList)
                    self.emitPOP() # pop environment pointer
                    self.emitPUSH(self.ACC)
                    self.emitCALL(funcLabel)
                    self.emitPOP()
                
        else:
            self.cgen(e[0]) # Generate code for function, loads pointer to closure into ACC
            self.callClosure(e[1:])

        self.fout.write("\n")

    def emitReadFile(self):
        beginLoopLabel = self.getNextLabel()
        endLoopLabel = self.getNextLabel()
        
        self.fout.write(".globl readFile\n")
        self.fout.write("readFile:\n")

        #Push "r" string
        self.emitMOV(self.getAtomLabel("r"), self.DATA)
        self.emitADD(self.WORD_SIZE, self.DATA)
        self.emitPUSH(self.DATA) # push "r"

        #open file
        self.emitADD(self.WORD_SIZE, self.ACC)
        self.emitPUSH(self.ACC)
        self.emitCALL("fopen")
        self.emitPOP()
        self.emitPOP() # pop "r"
        self.emitPUSH(self.ACC) # push file pointer

        #Special case for the first character
        
        #get character
        self.emitPUSH(0, self.STACK) # push file pointer
        self.emitCALL("_IO_getc")
        self.emitPOP()
        self.emitPUSH(self.ACC) # push character

        #get atom
        self.emitPUSH("$%d" % (self.WORD_SIZE + 2))
        self.emitCALL("malloc")
        self.emitPOP()
        self.emitMOVL("$-1", 0, self.ACC)
        self.emitMOVB(0, self.STACK, "%dl")
        self.emitMOVB("%dl", self.WORD_SIZE, self.ACC)
        self.emitMOVB("$0", self.WORD_SIZE + 1, self.ACC)
        self.emitPUSH(self.ACC) # push atom

        #get cons cell
        self.emitPUSH("$%d" % (2 * self.WORD_SIZE))
        self.emitCALL("malloc")
        self.emitPOP()
        self.emitMOV(0, self.STACK, self.DATA)
        self.emitMOV(self.DATA, 0, self.ACC)
        self.emitMOVL("$0", self.WORD_SIZE, self.ACC)

        self.emitMOV(self.ACC, self.COUNT) # COUNT register stores pointer to first atom
        self.emitMOV(self.ACC, self.DATA)
        self.emitADD(self.WORD_SIZE, self.DATA)
        
        self.emitPOP() # pop atom
        self.emitPOP(self.ACC) # pop character
        
        #Check to see if EOF
        
        self.emitLABEL(beginLoopLabel)
        
        self.fout.write("\tcmpb\t$-1, %al\n")
        self.fout.write("\tje\tlabel%i\n" % endLoopLabel)
        
        #Every other character
        
        #get character
        self.emitPUSH(self.DATA)
        self.emitPUSH(self.COUNT)
        self.emitPUSH(2*self.WORD_SIZE, self.STACK) # push file pointer
        self.emitCALL("_IO_getc")
        self.emitPOP()
        self.emitPOP(self.COUNT)
        self.emitPOP(self.DATA)
        self.emitPUSH(self.ACC) # push character
        
        #get atom
        self.emitPUSH(self.DATA)
        self.emitPUSH(self.COUNT)
        self.emitPUSH("$%d" % (self.WORD_SIZE + 2))
        self.emitCALL("malloc")
        self.emitPOP()
        self.emitPOP(self.COUNT)
        self.emitMOVL("$-1", 0, self.ACC)
        self.emitMOVB(self.WORD_SIZE, self.STACK, "%dl")
        self.emitMOVB("%dl", self.WORD_SIZE, self.ACC)
        self.emitMOVB("$0", self.WORD_SIZE + 1, self.ACC)
        self.emitPOP(self.DATA)
        self.emitPUSH(self.ACC) # push atom
        
        #get cons cell
        self.emitPUSH(self.DATA)
        self.emitPUSH(self.COUNT)
        self.emitPUSH("$%d" % (2 * self.WORD_SIZE))
        self.emitCALL("malloc")
        self.emitPOP()
        self.emitPOP(self.COUNT)
        self.emitMOV(self.WORD_SIZE, self.STACK, self.DATA)
        self.emitMOV(self.DATA, 0, self.ACC)
        self.emitMOVL("$0", self.WORD_SIZE, self.ACC)
        self.emitPOP(self.DATA)

        #append new cell to the list
        self.emitMOV(self.ACC, 0, self.DATA)
        self.emitMOV(self.ACC, self.DATA)
        self.emitADD(self.WORD_SIZE, self.DATA)
        
        self.emitPOP() # pop atom
        self.emitPOP(self.ACC) # pop character
        
        self.emitJMP(beginLoopLabel)
        
        self.emitLABEL(endLoopLabel)
        
        self.emitPOP() #pop file pointer
        self.emitMOV(self.COUNT, self.ACC)
        self.emitRET()

    def callClosure(self, argList):
        self.emitPUSH(self.ACC) # Push closure
        self.emitPUSH(self.WORD_SIZE, self.ACC) # Push environment pointer for the loadFuncArgs function
        self.fout.write("\n")
        
        self.loadFuncArgs(argList) # load pointer to list of current closure followed by arguments into ACC
        self.emitPOP() # Pop environment pointer
        self.fout.write("\n")
        
        self.emitPOP(self.DATA) # Pop closure into data
        self.emitPUSH(self.ACC) # Push argument pointer
        self.emitMOV(0, self.DATA, self.DATA) # Get the function pointer from the closure pointer
        self.emitCALL("*" + self.DATA) # Call Function
        self.emitPOP() # POP argument list pointer and closure        

    def allocClosure(self):
        self.emitPUSH(self.ACC)
                     
        self.fout.write("\n")

        #Allocate Closure
        self.emitPUSH("$%d" % (self.WORD_SIZE * 2))
        self.emitCALL("malloc")
        self.emitPOP()
        self.emitMOV(self.ACC, self.DATA)

        self.fout.write("\n")

        #Move function pointer into first closure spot
        self.emitPOP(self.ACC)
        self.emitMOV(self.ACC, 0, self.DATA)

        #Move enclosing lambda's environment pointer to the second closure spot
        self.emitMOV(2 * self.WORD_SIZE, self.BASE, self.ACC)
        self.emitMOV(self.ACC, self.WORD_SIZE, self.DATA)
        self.emitMOV(self.DATA, self.ACC)
        
    def function(self, argList, body, funcLabel):
        self.symTable.enterScope()

        for i in range(len(argList)):
            arg = argList[i].lexeme
            self.symTable.addVar(arg, self.WORD_SIZE * (i + 1))

        mainBuffer = self.fout
        self.fout = StringIO.StringIO()

        self.fout.write(".globl " + str(funcLabel) + "\n")
        self.emitFUNC(funcLabel)
        self.emitPUSH(self.BASE)
        self.emitMOV(self.STACK, self.BASE)

        self.cgen(body)

        self.emitPOP(self.BASE)
        self.emitRET()

        self.functionBuffer.write(self.fout.getvalue())
        self.fout = mainBuffer

        self.emitMOV("$" + funcLabel, self.ACC)
        self.symTable.exitScope()

    def cond(self, conds):
        endLabel = self.getNextLabel()
        for c in conds:
            trueLabel = self.getNextLabel()
            nextLabel = self.getNextLabel()
            self.cgen(c[0])
            self.emitSUB(self.getAtomLabel("#t"), self.ACC)
            self.emitMOV(self.ACC, self.COUNT)
            self.emitJECXZ(trueLabel)
            self.emitJMP(nextLabel)
            self.emitLABEL(trueLabel)
            self.cgen(c[1],)
            self.emitJMP(endLabel)
            self.emitLABEL(nextLabel)
        self.emitLABEL(endLabel)


    def eq(self):
        possNullLabel = self.getNextLabel()
        equalLabel    = self.getNextLabel()
        notEqualLabel = self.getNextLabel()
        endLabel      = self.getNextLabel()

        possAtomLabel = self.getNextLabel()
        bothAtomLabel = self.getNextLabel()

        self.emitPOP(self.ACC) # ACC holds second value
        self.emitPOP(self.DATA) # DATA holds first value

        self.emitMOV(self.DATA, self.COUNT)
        self.emitJECXZ(possNullLabel) # first is null, second unknown
        self.emitMOV(self.ACC, self.COUNT)
        self.emitJECXZ(notEqualLabel) # first not null, second is null

        ###### first not null, second not null #####
        # Must check to see if they're both strings
        self.emitPUSH(self.DATA)
        self.emitPUSH(self.ACC)
        self.emitMOV(0, self.DATA, self.DATA)
        self.emitMOV(0, self.ACC, self.ACC)

        self.emitCMP("$-1", self.DATA)
        self.emitJE(possAtomLabel)
        self.emitPOP()
        self.emitPOP()
        self.emitJMP(notEqualLabel) # first is a list, so they can't be equal

        self.emitLABEL(possAtomLabel)
        self.emitCMP("$-1", self.ACC)
        self.emitJE(bothAtomLabel) # both are atoms
        self.emitPOP()
        self.emitPOP()
        self.emitJMP(notEqualLabel) # first is an atom, second is a list

        self.emitLABEL(bothAtomLabel)
        self.emitPOP(self.ACC)
        self.emitPOP(self.DATA)
        self.emitADD(self.WORD_SIZE, self.ACC)
        self.emitADD(self.WORD_SIZE, self.DATA)
        self.emitPUSH(self.ACC)
        self.emitPUSH(self.DATA)
        self.emitCALL("strcmp")
        self.emitPOP()
        self.emitPOP()
        self.emitMOV(self.ACC, self.COUNT) # Count will be zero if they point to the same atom
        self.emitJECXZ(equalLabel) # equal atoms
        self.emitJMP(notEqualLabel) # unequal atoms
        ############################################
        
        self.emitLABEL(possNullLabel)
        self.emitMOV(self.ACC, self.COUNT)
        self.emitJECXZ(equalLabel) #first is null, second is null
        self.emitJMP(notEqualLabel) #first is null, second is not null

        self.emitLABEL(equalLabel)
        self.emitMOV(self.getAtomLabel("#t"), self.ACC)
        self.emitJMP(endLabel)

        self.emitLABEL(notEqualLabel)
        self.emitMOV("$0", self.ACC)
        
        self.emitLABEL(endLabel)


    def atom(self):
        atomLabel = self.getNextLabel()
        endLabel  = self.getNextLabel()

        self.emitPOP(self.COUNT)
        self.emitJECXZ(atomLabel) # Empty List - True
        self.emitMOV(0, self.COUNT, self.COUNT)
        self.emitCMP("$-1", self.COUNT)
        self.emitJE(atomLabel) # Atom - True
        self.emitMOV("$0", self.ACC)
        self.emitJMP(endLabel) # Non-empty list - False
        self.emitLABEL(atomLabel)
        self.emitMOV(self.getAtomLabel("#t"), self.ACC)
        self.emitLABEL(endLabel)


    def cons(self):
        self.emitPUSH("$%d" % (2 * self.WORD_SIZE))
        self.emitCALL("malloc")
        self.emitPOP()
        self.emitPOP(self.DATA)
        self.emitMOV(self.DATA, self.WORD_SIZE, self.ACC)
        self.emitPOP(self.DATA)
        self.emitMOV(self.DATA, 0, self.ACC)
        
    def cdr(self):
        self.emitPOP(self.ACC)
        self.emitADD(self.WORD_SIZE, self.ACC)
        self.emitMOV(0, self.ACC, self.ACC)

    def car(self):
        self.emitPOP(self.ACC)
        self.emitMOV(0, self.ACC, self.ACC)

    def quote(self, arg):
        if isinstance(arg, Token):
            self.emitMOV(self.getAtomLabel(arg.lexeme), self.ACC)
        else:
            if arg == []:
                self.emitMOV("$0", self.ACC)
            else:
                self.quote(arg[1:])
                self.emitPUSH(self.ACC)
                self.emitPUSH("$%d" % (2 * self.WORD_SIZE))
                self.emitCALL("malloc")
                self.emitPOP()
                self.emitPUSH(self.ACC)
                self.quote(arg[0])
                self.emitPOP(self.DATA)
                self.emitMOV(self.ACC, 0, self.DATA)
                self.emitMOV(0, self.STACK, self.COUNT)
                self.emitMOV(self.COUNT, self.WORD_SIZE, self.DATA)
                self.emitPOP()
                self.emitMOV(self.DATA, self.ACC)

            
    def __init__(self, fout):
        self.atomLabels = {}

        self.atomLabelID = 0
        self.labelID = 0
        self.funcLabelID = 0

        self.ATOM_ID = -1

        self.systemCalls = ("print!", "read-file!")

        self.fout = fout
        self.functionBuffer = StringIO.StringIO()
        self.symTable = SymbolTable()

        self.addAtom("(")
        self.addAtom(")")
        self.addAtom(" ")
        self.addAtom("\\n")
        self.addAtom("#t")
        self.addAtom("r") # for reading from files

    def getNextLabel(self):
        self.labelID += 1
        return self.labelID - 1

    def getNextFuncLabel(self):
        self.funcLabelID += 1
        return self.funcLabelID - 1

    def addAtom(self, atom):
        if atom not in self.atomLabels:
            self.atomLabels[atom] = self.atomLabelID
            self.atomLabelID += 1

    def getAtomLabel(self, atom):
        return "$.atom%d" % (self.atomLabels[atom])

    def findAtoms(self, tokens):
        for token in tokens:
            if token.lexeme not in ["(", ")"]:
                self.addAtom(token.lexeme)
                
    def emitAtoms(self):
        for atom in self.atomLabels:
            self.fout.write(".atom" + str(self.atomLabels[atom]) + ":\n")
            self.fout.write("\t.long %d\n" % self.ATOM_ID)
            self.fout.write("\t.string \"" + str(atom) + "\"\n")

    def emitCodeStart(self):
        #libc requires a main entry point
        self.fout.write("\t.text\n")
        self.fout.write(".globl main\n")
        self.fout.write("main:\n")

        #I don't know what these things do
        self.emitLEA(self.WORD_SIZE, self.STACK, self.COUNT)
        self.fout.write("\tand\t$-16, %esp\n")
        self.emitPUSH(-self.WORD_SIZE, self.COUNT)
        
        #save registers
        self.emitPUSH(self.BASE)
        self.emitMOV(self.STACK, self.BASE)
        self.emitPUSH(self.COUNT)

    def emitCodeEnd(self):
        #recover registers
        self.emitPOP(self.COUNT)
        self.emitPOP(self.BASE)
        self.emitLEA(-self.WORD_SIZE, self.COUNT, self.STACK)
        self.emitRET()
        self.emitFuncBuffer()
        self.emitPengPrint()
        self.emitReadFile()

    def emitFuncBuffer(self):
        self.fout.write(self.functionBuffer.getvalue())

    def emitPrintAtom(self, atom):
        self.emitMOV(self.getAtomLabel(atom), self.DATA)
        self.emitADD(self.WORD_SIZE, self.DATA)
        self.emitPUSH(self.DATA)
        self.emitCALL("printf")
        self.emitPOP()

    # pointer to expression to print should be in the accumulator
    # right now this assumes that the list contains only atoms
    def emitPengPrint(self):
        self.fout.write(".globl pengPrint\n")
        self.fout.write("pengPrint:\n")
        isNull = self.getNextLabel()
        notNull = self.getNextLabel()
        atomJumpLabel = self.getNextLabel()
        endLabel = self.getNextLabel()

        self.emitMOV(self.ACC, self.COUNT)
        self.emitJECXZ(isNull)
        self.emitJMP(notNull)
        self.emitLABEL(isNull)
        self.emitPrintAtom("(")
        self.emitPrintAtom(")")
        self.emitRET()
        
        self.emitLABEL(notNull)
        self.emitMOV(0, self.ACC, self.COUNT)
        self.emitCMP("$-1", self.COUNT)
        self.emitJE(atomJumpLabel)

        #It's a list

        #push list pointer
        self.emitPUSH(self.ACC)

        #print open paren
        self.emitPrintAtom("(")

        loopLabel = self.getNextLabel()
        endLoopLabel = self.getNextLabel()
        atomPrintLabel = self.getNextLabel()

        self.emitLABEL(loopLabel)
        
        #pop list pointer
        self.emitPOP(self.DATA)
        #push next list pointer
        self.emitMOV(self.DATA, self.COUNT)
        self.emitADD(self.WORD_SIZE, self.COUNT)
        self.emitPUSH(0, self.COUNT)
        #get object pointer in DATA
        self.emitMOV(0, self.DATA, self.ACC)
        #load data held in object pointer into Count, for strings this will be 0
        self.emitCALL("pengPrint")
        #pop the next list pointer from the stack
        self.emitPOP(self.COUNT)
        #branch here if the next list pointer is null
        self.emitJECXZ(endLoopLabel)
        #push next list pointer
        self.emitPUSH(self.COUNT)
        #print space
        self.emitPrintAtom(" ")
        #print next atom
        self.emitJMP(loopLabel)

        #marks end of list
        self.emitLABEL(endLoopLabel)        
        self.emitPrintAtom(")")
        self.emitJMP(endLabel)

        #It's an atom

        self.emitLABEL(atomJumpLabel)
        self.emitADD(self.WORD_SIZE, self.ACC)
        self.emitPUSH(self.ACC)
        self.emitCALL("printf")
        self.emitPOP()

        self.emitLABEL(endLabel)
        self.emitRET()
                     


    def emitJE(self, *args):
        if len(args) == 1:
            self.fout.write("\tje\tlabel%d\n" % args)

    def emitCMP(self, *args):
        if len(args) == 2:
            self.fout.write("\tcmp\t%s, %s\n" % args)

    def emitSUB(self, *args):
        if len(args) == 2:
            self.fout.write("\tsub\t%s, %s\n" % args)

    def emitRET(self):
        self.fout.write("\tret\n")

    def emitFUNC(self, *args):
        if len(args) == 1:
            self.fout.write("%s:\n" % args)

    def emitLABEL(self, *args):
        if len(args) == 1:
            self.fout.write("label%d:\n" % args)

    def emitJMP(self, *args):
        if len(args) == 1:
            self.fout.write("\tjmp\tlabel%d\n" % args)

    def emitJECXZ(self, *args):
        if len(args) == 1:
            self.fout.write("\tjecxz\tlabel%d\n" % args)                         

    def emitADD(self, *args):
        if len(args) == 2:
            if type(args[0]) == int:
                self.fout.write("\tadd\t$%d, %s\n" % args)
            else:
                self.fout.write("\tadd\t%s, %s\n" % args)
            

    def emitPOP(self, *args):
        if len(args) == 0:
            self.emitADD(self.WORD_SIZE, self.STACK)
        if len(args) == 1:
            self.fout.write("\tpop\t%s\n" % args)

    def emitCALL(self, name):
        self.fout.write("\tcall\t%s\n" % name)

    def emitMOV(self, *args):
        if len(args) == 2:
            self.fout.write("\tmov\t%s, %s\n" % args)
        if len(args) == 3 and type(args[0]) == int:
            self.fout.write("\tmov\t%d(%s), %s\n" % args)
        if len(args) == 3 and type(args[1]) == int:
            self.fout.write("\tmov\t%s, %d(%s)\n" % args)

    def emitMOVB(self, *args):
        if len(args) == 2:
            self.fout.write("\tmovb\t%s, %s\n" % args)
        if len(args) == 3 and type(args[0]) == int:
            self.fout.write("\tmovb\t%d(%s), %s\n" % args)
        if len(args) == 3 and type(args[1]) == int:
            self.fout.write("\tmovb\t%s, %d(%s)\n" % args)

    def emitMOVL(self, *args):
        if len(args) == 2:
            self.fout.write("\tmovl\t%s, %s\n" % args)
        if len(args) == 3 and type(args[0]) == int:
            self.fout.write("\tmovl\t%d(%s), %s\n" % args)
        if len(args) == 3 and type(args[1]) == int:
            self.fout.write("\tmovl\t%s, %d(%s)\n" % args)


    def emitPUSH(self, *args):
        if len(args) == 1:
            self.fout.write("\tpush\t%s\n" % args)
        if len(args) == 2:
            self.fout.write("\tpush\t%d(%s)\n" % args)

    def emitPUSHB(self, *args):
        if len(args) == 1:
            self.fout.write("\tpushb\t%s\n" % args)
        if len(args) == 2:
            self.fout.write("\tpushb\t%d(%s)\n" % args)


    def emitLEA(self, *args):
        if len(args) == 2:
            self.fout.write("\tlea\t%s, %s\n" % args)
        if len(args) == 3 and type(args[0]) == int:
            self.fout.write("\tlea\t%d(%s), %s\n" % args)
        if len(args) == 3 and type(args[1]) == int:
            self.fout.write("\tlea\t%s, %d(%s)\n" % args)

    
def createTree(tokens):
    sexps = []
    tokenNum = 0
    while tokenNum < len(tokens):
        if tokens[tokenNum].tokenID != Token.OPEN_PAREN_ID:
            print "Expected open paren at line %d" % (tokens[tokenNum].lineno)
            sys.exit()
        close_index = findCloseParen(tokens, tokenNum)
        if close_index == -1:
            print "Expected close paren for the open paren at line %d" % (tokens[tokenNum].lineno)
            sys.exit()
        sexps.append(extractAST(tokens[tokenNum : close_index + 1]))
        tokenNum = close_index + 1
    return sexps

if __name__ == '__main__':
    inputFileName = sys.argv[1]
    inputRootName = inputFileName.split(".")[0]

    fin  = open(inputFileName, "r")
    fout = open(inputRootName + ".s", "w")

    inputLines = fin.readlines()

    finLines = []

    #import other files
    for line in inputLines:
        if line.find(";load-file ") == 0:
            filename = line[len(";load-file "):].strip()
            tempfin = open(filename, "r")
            for line in tempfin.readlines():
                finLines.append(line)

    for line in inputLines:
        finLines.append(line)

    tokens = extractTokens(finLines)
    sexps = createTree(tokens)

    cgen = CgenSupport(fout)
    cgen.symTable.enterScope()
    cgen.findAtoms(tokens)
    cgen.emitAtoms()
    cgen.emitCodeStart()
    cgen.fout.write("\n")
    for sexp in sexps:
        if isinstance(sexp[0], Token) and sexp[0].lexeme == "define":
            name = sexp[1].lexeme
            argLen = len(sexp[2][1])
            funcLabel = cgen.getNextFuncLabel()
            #symbol for a function is a tuple of its name and size of argument list.
            #I should make this cleaner by making a function class or something.
            cgen.symTable.addVar(name, (("func%d" % funcLabel), argLen))

    for sexp in sexps:
        cgen.cgen(sexp)

    cgen.emitCodeEnd()
    cgen.symTable.exitScope()

    fin.close()
    fout.close()

    os.system("gcc -o %s %s" % (inputFileName.split(".")[0], inputFileName.split(".")[0] + ".s"))
